今天來看 useDebounceFn API 相關的單元測試~
// src/utils/filter.test.js
import { ref } from 'vue'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createFilterWrapper, debounceFilter } from '@/utils/filter'
describe('filters', () => {
beforeEach(() => {
vi.useFakeTimers()
})
// 測試案例放這邊
})
主要是在每個測試案例開始前,mock 案例中會用到的所有 timer。測試案例都會放在註解那個層級位置。
// src/utils/filter.test.js
it('should debounce', () => {
const debouncedFilterSpy = vi.fn()
const filter = createFilterWrapper(debounceFilter(1000), debouncedFilterSpy)
setTimeout(filter, 200)
vi.runAllTimers()
setTimeout(filter, 500)
vi.advanceTimersByTime(500)
expect(debouncedFilterSpy).toHaveBeenCalledOnce()
})
debouncedFilterSpy 是在什麼時候被觸發一次的呢?主要是這行 vi.runAllTimers()
,他會執行 setTimeout(filter, 200) 中的 filter,也就是不用真的等 200ms,而 filter 被執行代表 debounceFilter 中的 1000ms timer 也啟動了,這個也一樣因為 vi.runAllTimers()
的關係,不用真的等 1000ms,會直接執行 debouncedFilterSpy。
接下來在測試 setTimeout(filter, 500) ,使用 vi.advanceTimersByTime(500)
讓時間過了 500ms 後,因為還不到我們給 debounceFilter 設定的 1000ms,所以這個不會觸發 debouncedFilterSpy。
it('should debounce twice', () => {
const debouncedFilterSpy = vi.fn()
const filter = createFilterWrapper(debounceFilter(500), debouncedFilterSpy)
setTimeout(filter, 500)
vi.advanceTimersByTime(500)
setTimeout(filter, 1000)
vi.advanceTimersByTime(2000)
expect(debouncedFilterSpy).toHaveBeenCalledTimes(2)
})
這個測試滿單純的,測試 timer 時間到的時候,有沒有執行 debouncedFilterSpy,另外 vi.advanceTimersByTime(2000)
這邊的 2000,改成 1500 也會過,因為這個案例 debounceFilter 設定的 ms 是 500。
it('should resolve & reject debounced fn', async () => {
const debouncedSum = createFilterWrapper(
debounceFilter(500, { rejectOnCancel: true }),
(a, b) => a + b,
)
const five = debouncedSum(2, 3)
let nine
setTimeout(() => {
nine = debouncedSum(4, 5)
}, 200)
vi.runAllTimers()
await expect(five).rejects.toBeUndefined()
await expect(nine).resolves.toBe(9)
})
這個是在測試 rejectOnCancel 為 true 的情境,被取消執行的 Promise 是否有 reject、成功執行的是否有 resolve。這個案例 debounceFilter 設定的 ms 是 500ms,所以在執行vi.runAllTimers()
的時候,five
這個 Promise 會變成 rejected 狀態,nine
則是會變成 resolved。
it('should debounce with ref', () => {
const debouncedFilterSpy = vi.fn()
const debounceTime = ref(0)
const filter = createFilterWrapper(debounceFilter(debounceTime), debouncedFilterSpy)
filter()
debounceTime.value = 500
filter() // <- 這個會被取消
setTimeout(filter, 200)
vi.runAllTimers()
expect(debouncedFilterSpy).toHaveBeenCalledTimes(2)
})
測試 debounceTime 是 ref 物件的情境,第一個 filter()
因為 debounceTime.value 是 0,所以這個會直接執行 debouncedFilterSpy。在這之後把 debounceTime.value 改成 500ms,接下來的兩個 filter()
,timer 都已經被改成 500ms,所以在 vi.runAllTimers()
執行後,有註解的那個 filter()
會被取消,最後 debouncedFilterSpy 會被執行兩次。也就是說,如果 ref 失效的話,debounceTime.value 會一直都是 0,最後結果會是非預期的三次。
GitHub PR:https://github.com/RhinoLee/30days_vue/pull/18/files
今天透過 unit test 原始碼學習了一些 timer 情境的測試方式,覺得對於 vitest API 的掌握度也滿重要的,文件可能要再看熟一點 XD
useDebounceFn 到今天就告一段落了,明天會開始看 useScroll~